package es.xpt.ysf.commons.pos;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.StringTokenizer;
import es.xpt.ysf.configuration.Configuration;
import es.xpt.ysf.configuration.ConfigurationParameterNames;
import es.xpt.ysf.logging.LogManager;

/**
 * Clase que modela los datos necesarios para una conexin con un TPV del BBVA
 */
public class PosBBVA { // NO_UCD

	
	
	private String merchantTerminal;
	private String merchantID;	
	private String merchantURL;
	private String url_notification;
	private String url_ok;
	private String operationNumber;
	private String bookingId;
	private BigDecimal amount;
	private String environment;
	private String application;
	private String productDescription;
	private String holder;
	private Configuration c;
	
	public PosBBVA(String merchantID,String url_notification, String merchantTerminal,String merchantURL, 
			String urlOk, String urlNok, String operationNumber, BigDecimal amount, String productDescription, String holder,String bookingId) {
		super();
		c = new Configuration ("globalConfigurations");
		this.merchantID = merchantID;
		this.merchantTerminal = merchantTerminal;
		this.merchantURL = merchantURL;
		this.url_notification = url_notification;
		this.bookingId=bookingId;
		this.url_ok = urlOk;
		url_ok+=((url_ok.contains("?")) ? "&=":"?") + "bookingId="+this.bookingId;
		this.operationNumber = operationNumber;
		this.amount = amount;
		this.productDescription = (productDescription!=null && productDescription.length()>125) ? productDescription.substring(0, 125): productDescription;
		this.holder = (holder!=null && holder.length()>60) ? holder.substring(0, 60): holder;
	}
	
	public PosBBVA() {
		super();
		c = new Configuration ("globalConfigurations");
		//Leer todos los parmetros del baseConfiguration
		this.merchantID = c.getString(ConfigurationParameterNames.BBVA_MERCHANT);
		this.merchantTerminal = c.getString(ConfigurationParameterNames.BBVA_MERCHANT_TERMINAL);
		this.url_notification = c.getString(ConfigurationParameterNames.BBVA_URLNOTIFICATION);
		this.merchantURL = c.getString(ConfigurationParameterNames.BBVA_MERCHANT_URL);
		this.url_ok = c.getString(ConfigurationParameterNames.BBVA_URLOK);
	}
	
	public PosBBVA(String operationNumber,BigDecimal amount,String environment,String application,String bookingId) {
		this();
		this.operationNumber = operationNumber;
		this.amount = amount;		
		this.environment=environment;
		this.application=application;
		this.bookingId=bookingId;
		url_ok+=((url_ok.contains("?")) ? "&=":"?") + "bookingId="+this.bookingId;
		
	}	
	
	public String getRequestForm() {
		
		String urlPos = c.getString(ConfigurationParameterNames.BBVA_URL_TPV);
		String encrytpPassword = c.getString(ConfigurationParameterNames.BBVA_ENCRYPT_PASS);
				
		StringBuffer form=null;
		
		if (urlPos!=null && !urlPos.trim().equals("")) {

			try {
				String transId=calculateTransId(operationNumber);
				String secretKey=desofuscarPalabraSecreta(encrytpPassword, "un1pl4y4"+merchantID.substring(0,9)+"***");
				String signature=getSHA1(merchantTerminal+merchantID+transId+replaceNoNum(amount.toString())+"978"+operationNumber+secretKey);
				StringBuffer request=new StringBuffer(				
				"<tpv>"
		        + "<oppago>"
				+ "<idterminal>"+merchantTerminal+"</idterminal>"
		 		+ "<idcomercio>"+merchantID+"</idcomercio>"
		 		+ "<idtransaccion>"+transId+"</idtransaccion>"
				+ "<moneda>978</moneda>"
				+ "<importe>"+amount.toString()+"</importe>"
				+ "<urlcomercio>"+url_notification+"</urlcomercio>"
				+ "<idioma>es</idioma>"
                + "<pais>ES</pais>"
                + "<canal>1</canal>"
                + "<soporte>1</soporte>"
                + "<urlredir>"+url_ok+"</urlredir>"
        	    + "<localizador>"+operationNumber+"</localizador>"	
        	    + "<firma>"+signature+"</firma>"
        	    + "</oppago>"
        	    + "</tpv>");
				
				
				
				form = new StringBuffer( "<html><head><script language=\"javascript\">function redirect() { window.document.formulario.submit();	}</script></head><body onload=\"redirect();\">");
		  		form.append( "<form name=\"formulario\" method=\"post\" target=\"_top\" action=\"" + urlPos + "\">\n");
		  		form.append( "<input type=\"hidden\" name=\"peticion\" value=\"" + request + "\">\n"  );		  		
		  		form.append("</form></body></html>");
			} catch (Exception e) {
				LogManager.getLog(getClass()).error("Error while generating url:"+e.getMessage(), e);
			}
		} else {
			LogManager.getLog(getClass()).error("Error while recovering configuration url value for BBVA POS");
		}
		return form.toString();
	}
	
	private String calculateTransId(String operationNumber) {
		if (operationNumber.contains("_")) {
			operationNumber = replaceNoNum(operationNumber);
		}
		operationNumber="00000000000000000000"+operationNumber;
		return operationNumber.substring(operationNumber.length()-12);
	}

	/**
	 * @return the merchantID
	 */
	public String getMerchantID() {		
		return merchantID;
	}

	/**
	 * @param merchantID the merchantID to set
	 */
	public void setMerchantID(String merchantID) {
		this.merchantID = merchantID;
	}

	/**
	 * @return the url_ok
	 */
	public String getUrl_ok() {
		return url_ok;
	}

	/**
	 * @param urlOk the url_ok to set
	 */
	public void setUrl_ok(String urlOk) {
		url_ok = urlOk;
	}

	/**
	 * @return the amount
	 */
	public BigDecimal getAmount() {
		return amount;
	}

	/**
	 * @param amount the amount to set
	 */
	public void setAmount(BigDecimal amount) {
		this.amount = amount;
	}

	/**
	 * @return the operationNumber
	 */
	public String getOperationNumber() {
		return operationNumber;
	}

	/**
	 * @param operationNumber the operationNumber to set
	 */
	public void setOperationNumber(String operationNumber) {
		this.operationNumber = operationNumber;
	}

	/**
	 * @return the c
	 */
	public Configuration getC() {
		return c;
	}

	/**
	 * @param c the c to set
	 */
	public void setC(Configuration c) {
		this.c = c;
	}

	/**
	 * @return the environment
	 */
	public String getEnvironment() {
		return environment;
	}

	/**
	 * @param environment the environment to set
	 */
	public void setEnvironment(String environment) {
		this.environment = environment;
	}

	/**
	 * @return the application
	 */
	public String getApplication() {
		return application;
	}

	/**
	 * @param application the application to set
	 */
	public void setApplication(String application) {
		this.application = application;
	}
	
	/**
	 * @return the merchantTerminal
	 */
	public String getMerchantTerminal() {
		return merchantTerminal;
	}

	/**
	 * @param merchantTerminal the merchantTerminal to set
	 */
	public void setMerchantTerminal(String merchantTerminal) {
		this.merchantTerminal = merchantTerminal;
	}

	/**
	 * @return the productDescription
	 */
	public String getProductDescription() {
		return productDescription;
	}

	/**
	 * @param productDescription the productDescription to set
	 */
	public void setProductDescription(String productDescription) {
		this.productDescription = productDescription;
	}

	/**
	 * @return the holder
	 */
	public String getHolder() {
		return holder;
	}

	/**
	 * @param holder the holder to set
	 */
	public void setHolder(String holder) {
		this.holder = holder;
	}
	
	public String getMerchantURL() {
		return merchantURL;
	}

	public void setMerchantURL(String merchantURL) {
		this.merchantURL = merchantURL;
	}
	
	//Utilidades Venidas de los propios ejemplos de BBVA
	
	/**
	 * @param url_notification the url_notification to set
	 */
	public void setUrl_notification(String url_notification) {
		this.url_notification = url_notification;
	}

	/**
	 * @return the url_notification
	 */
	public String getUrl_notification() {
		return url_notification;
	}

	/**
     * Desofusca la palabra secreta con la clave que introdujo el usuario al solicitarla.
     * @param palabraSecOfuscHex la palabra secreta ofuscada y expresada en hexadecimal
     * @param claveOfuscadoraLong la clave ofuscadora rellena con el idComercio hasta que tenga
     * igual longitud que la palabra secreta expresada en claro.
     * @return La palabra secreta en claro
     */
    private String desofuscarPalabraSecreta(String palabraSecOfuscHex, String claveOfuscadoraLong) throws UnsupportedEncodingException
    {
        try
        {
            //se expresa en bytes las claves de las que se hara el XOR
            byte[] claveOfuscadoraLongBytes = claveOfuscadoraLong.getBytes("ISO-8859-1");
            byte[] palabraSecOfuscBytes =  hexStringToByteArray(palabraSecOfuscHex, ";");

            byte[] palabraSecretaBytes = xorArrayBytes(claveOfuscadoraLongBytes, palabraSecOfuscBytes);
            //Se pasa la palabra secreta de bytes a ASCIII
            String palabraSecreta =  new String(palabraSecretaBytes, "ISO-8859-1");

            return palabraSecreta;
        }
        catch (Exception e) 
        {
            return null;
        }
    }

    /**
     * Permite hacer un XOR entre dos array de bytes.
     * @param operador1 Operador de la izquierda de en el XOR.
     * @param operador2 Operador de la derecha de en el XOR.
     * @exception IllegalArgumentException Si la longitud en bytes del operador de la izquierda es mayor que el de la derecha.
     */
    private static byte[] xorArrayBytes(byte[] operador1, byte[] operador2) throws IllegalArgumentException
    {
        if (operador1.length > operador2.length)
            throw new IllegalArgumentException("El operador1 es de longitud mayor que el operador2");
        byte[] resultado = new byte[operador1.length];
        for (int cont = 0; cont < operador1.length; cont++)
        {
            resultado[cont] = (byte)(operador1[cont] ^ operador2[cont]);
        };
        return resultado;
    }

    /**
     * 
     */
    private static byte[] hexStringToByteArray(String lista, String separador)
        throws NumberFormatException
        {
            StringTokenizer parser = new StringTokenizer(lista, separador);
            byte[] buffer = new byte[parser.countTokens()];
            int index = 0;
            while (parser.hasMoreTokens())
            {
                buffer[index] = (byte)Integer.parseInt(parser.nextToken(), 16);
                index++;
            };
            return buffer;
    }

    
    //Utiles de firma
    private final static String HEX_CHR = "0123456789ABCDEF";
    /*
    * Convierte un numero 32-bit a una cadena hexadecimal con ms-byte primero
    */
    private String hex(int num)
     {

       String str= "";
       for(int j = 7; j >= 0; j--)
       {
         str +=  HEX_CHR.charAt((num >> (j * 4)) & 0x0F);
       }

       return str;
     }


   /*
    * Aade enteros, "wrapping" a 2^32. Se usan operaciones de 16-bit para evitar problemas con algunos navegadores.
    */
    private int safe_add(int x, int y)
     {
       int lsw = (x & 0xFFFF) + (y & 0xFFFF);
       int msw = (x >> 16) + (y >> 16) + (lsw >> 16);
       return (msw << 16) | (lsw & 0xFFFF);
     }


     /*
     * Se rota un numero 32-bit a la izquierda
     */
    private int rol(int num, int cnt)
     {
      // System.out.println("Entro en rol con valores: num--> " + num + "y cnt--> " + cnt);
       return (num << cnt) | (num >>> (32 - cnt));
     }

     /*
     * Realiza la apropiada funcion combinacion triple para la iteracion actual
     */
    private int ft(int t, int b, int c, int d)
     {
       if(t < 20)
         return (b & c) | ((~b) & d);
       if(t < 40) return
         b ^ c ^ d;
       if(t < 60)
         return (b & c) | (b & d) | (c & d);
       return b ^ c ^ d;
     }


     /*
     * Determina la apropiada constante a aadir para la iteracion actual
     */
    private int kt(int t)
     {
       return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
            (t < 60) ? -1894007588 : -899497514;
     }


   /*
    * Devuelve la representacion hexadecimal del SHA-1 de la cadena pasada como parametro
    */
    private String getSHA1(String str)
   {

     Integer[]  x = str2blks_SHA1(str);
     Integer[]  w = new Integer[80];

     int a =  1732584193;
     int b = -271733879;
     int c = -1732584194;
     int d =  271733878;
     int e = -1009589776;

     for(int i = 0; i < x.length; i += 16)
     {
       int olda = a;
       int oldb = b;
       int oldc = c;
       int oldd = d;
       int olde = e;

       for(int j = 0; j < 80; j++)
       {
         if(j < 16)
           w[j] = x[i + j];
         else
           w[j] = Integer.valueOf(rol(w[j-3].intValue() ^ w[j-8].intValue() ^ w[j-14].intValue() ^ w[j-16].intValue(), 1));
         int t = safe_add(safe_add(rol(a, 5), ft(j, b, c, d)), safe_add(safe_add(e, w[j].intValue()), kt(j)));
         e = d;
         d = c;
         c = rol(b, 30);
         b = a;
         a = t;
       }

       a = safe_add(a, olda);
       b = safe_add(b, oldb);
       c = safe_add(c, oldc);
       d = safe_add(d, oldd);
       e = safe_add(e, olde);
     }
     return (hex(a) + hex(b) + hex(c) + hex(d) + hex(e));

   }

    /*
    * Convierte una cadena a una secuencia de bloques de 16 palabras, almacenadas como array.
    * Aade bits de relleno y la longitud, como se describe en el SHA1 estandar
    */

    private Integer[] str2blks_SHA1(String str)
     {
      int nblk = ((str.length() + 8) >> 6) + 1;
       Integer[] blks = new Integer[nblk * 16];
       for(int i = 0; i < nblk * 16; i++)
         blks[i] = Integer.valueOf(0);

       int i = 0;
       for(i = 0; i < str.length(); i++)
       {
         blks[i >> 2] = Integer.valueOf(blks[i >> 2].intValue() | (Character.valueOf(str.charAt(i)).hashCode() << (24 - (i % 4) * 8)));
       }

      blks[i >> 2] = Integer.valueOf(blks[i >> 2].intValue() | 0x80 << (24 - (i % 4) * 8));
      blks[nblk * 16 - 1] = Integer.valueOf(str.length() * 8);

       return blks;

     }
    
    /**
     * Devuelve el String resultante de eliminar del String pasado 
     * como parmetro los caracteres que no son numricos.
     */
    public static String replaceNoNum(String cadena)
    {
        StringBuffer buffer = new StringBuffer(cadena);
        int cont = 0;
        while (cont < buffer.length())
        {
            if (!Character.isDigit(buffer.charAt(cont)))
            {
                buffer.deleteCharAt(cont);
            }
            else
                cont++;
        }
        return buffer.toString();
    }

	/**
	 * @param bookingId the bookingId to set
	 */
	public void setBookingId(String bookingId) {
		this.bookingId = bookingId;
	}

	/**
	 * @return the bookingId
	 */
	public String getBookingId() {
		return bookingId;
	}
}
